/**
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
 */

package net.sourceforge.pmd.eclipse.runtime.builder;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.CoreException;

import net.sourceforge.pmd.Rule;
import net.sourceforge.pmd.RuleSet;
import net.sourceforge.pmd.eclipse.plugin.PMDPlugin;
import net.sourceforge.pmd.eclipse.runtime.PMDRuntimeConstants;
import net.sourceforge.pmd.eclipse.ui.PMDUiConstants;
import net.sourceforge.pmd.eclipse.ui.model.AbstractPMDRecord;
import net.sourceforge.pmd.eclipse.ui.model.FileRecord;
import net.sourceforge.pmd.eclipse.ui.model.MarkerRecord;
import net.sourceforge.pmd.eclipse.ui.model.RootRecord;

/**
 * 
 * @author Brian Remedios
 */
public final class MarkerUtil {

    public static final IMarker[] EMPTY_MARKERS = new IMarker[0];

    private static Map<String, Rule> rulesByName;

    private MarkerUtil() {
    }

    public static boolean hasAnyRuleMarkers(IResource resource) throws CoreException {

        final boolean[] foundOne = new boolean[] { false };

        IResourceVisitor ruleMarkerFinder = new IResourceVisitor() {
            @Override
            public boolean visit(IResource resource) {

                if (foundOne[0]) {
                    return false;
                }

                if (resource instanceof IFile) {

                    for (String markerType : PMDRuntimeConstants.RULE_MARKER_TYPES) {
                        IMarker[] ruleMarkers = null;
                        try {
                            ruleMarkers = resource.findMarkers(markerType, true, IResource.DEPTH_INFINITE);
                        } catch (CoreException ignored) {
                            // what do to?
                        }
                        if (ruleMarkers != null && ruleMarkers.length > 0) {
                            foundOne[0] = true;
                            return false;
                        }
                    }
                }

                return true;
            }
        };

        try {
            resource.accept(ruleMarkerFinder);
        } catch (CoreException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return foundOne[0];
    }

    private static IProject projectFor(IResource resource) {

        if (resource instanceof IWorkspaceRoot) {
            return null;
        }
        if (resource instanceof IProject) {
            return (IProject) resource;
        }
        return projectFor(resource.getParent());
    }

    public static Set<IProject> commonProjectsOf(IMarker[] markers) {

        Set<IProject> projects = new HashSet<>();

        for (IMarker marker : markers) {
            IProject project = projectFor(marker.getResource());
            if (project != null) {
                projects.add(project);
            }
        }

        return projects;
    }

    public static String ruleNameFor(IMarker marker) {
        return marker.getAttribute(PMDUiConstants.KEY_MARKERATT_RULENAME, "");
    }

    public static int rulePriorityFor(IMarker marker) throws CoreException {
        return (Integer) marker.getAttribute(PMDUiConstants.KEY_MARKERATT_PRIORITY);
    }

    public static String messageFor(IMarker marker, String defaultValue) {
        return marker.getAttribute(IMarker.MESSAGE, defaultValue);
    }

    public static Long createdOn(IMarker marker, long onErrorValue) {

        try {
            return (Long) marker.getCreationTime();
        } catch (CoreException ce) {
            return onErrorValue;
        }
    }

    public static int rulePriorityFor(IMarker marker, int defaultValue) {
        return marker.getAttribute(PMDUiConstants.KEY_MARKERATT_PRIORITY, defaultValue);
    }

    public static boolean doneState(IMarker marker, boolean defaultValue) {
        return marker.getAttribute(IMarker.DONE, defaultValue);
    }

    public static int deleteViolationsOf(String ruleName, IResource resource) {

        try {
            IMarker[] markers = findAllMarkers(resource);
            if (markers.length == 0) {
                return 0;
            }

            List<IMarker> matches = new ArrayList<>(markers.length);

            for (IMarker marker : markers) {
                String name = ruleNameFor(marker);
                if (ruleName.equals(name)) {
                    matches.add(marker);
                }
            }

            markers = new IMarker[matches.size()];
            matches.toArray(markers);
            resource.getWorkspace().deleteMarkers(markers);

            return markers.length;
        } catch (CoreException ex) {
            return 0;
        }
    }

    public static List<IMarkerDelta> markerDeltasIn(IResourceChangeEvent event) {

        List<IMarkerDelta> deltas = new ArrayList<>();
        for (String markerType : PMDRuntimeConstants.RULE_MARKER_TYPES) {
            IMarkerDelta[] deltaArray = event.findMarkerDeltas(markerType, true);
            for (IMarkerDelta delta : deltaArray) {
                deltas.add(delta);
            }
        }

        return deltas;
    }

    public static List<Rule> rulesFor(IMarker[] markers) {

        List<Rule> rules = new ArrayList<>(markers.length);
        RuleSet ruleset = PMDPlugin.getDefault().getPreferencesManager().getRuleSet();

        for (IMarker marker : markers) {
            String name = ruleNameFor(marker);
            if (StringUtils.isBlank(name)) {
                continue;
            }
            Rule rule = ruleset.getRuleByName(name);
            if (rule == null) {
                continue;
            }
            rules.add(rule);
        }

        return rules;
    }

    /**
     * Returns the name of the rule that is common to all markers or null if any
     * one of them differ.
     * 
     * @param IMarker[]
     *            markers
     * @return String
     */
    public static String commonRuleNameAmong(IMarker[] markers) {

        String ruleName = ruleNameFor(markers[0]);
        for (int i = 1; i < markers.length; i++) {
            if (!ruleName.equals(ruleNameFor(markers[i]))) {
                return null;
            }
        }

        return ruleName;
    }

    public static void deleteAllMarkersIn(IResource resource) throws CoreException {
        deleteMarkersIn(resource, PMDRuntimeConstants.ALL_MARKER_TYPES);
    }

    public static void deleteMarkersIn(IResource resource, String markerType) throws CoreException {
        deleteMarkersIn(resource, new String[] { markerType });
    }

    public static void deleteMarkersIn(IResource resource, String[] markerTypes) throws CoreException {
        for (String markerType : markerTypes) {
            resource.deleteMarkers(markerType, true, IResource.DEPTH_INFINITE);
        }
        PMDPlugin.getDefault().removedMarkersIn(resource);
    }

    public static IMarker[] findAllMarkers(IResource resource) throws CoreException {
        return findMarkers(resource, PMDRuntimeConstants.ALL_MARKER_TYPES);
    }

    public static IMarker[] findMarkers(IResource resource, String markerType) throws CoreException {
        return findMarkers(resource, new String[] { markerType });
    }

    public static IMarker[] findMarkers(IResource resource, String[] markerTypes) throws CoreException {

        List<IMarker> markerList = new ArrayList<>();

        for (String markerType : markerTypes) {
            for (IMarker marker : resource.findMarkers(markerType, true, IResource.DEPTH_INFINITE)) {
                markerList.add(marker);
            }
        }

        IMarker[] markerArray = new IMarker[markerList.size()];
        return markerList.toArray(markerArray);
    }

    public static Set<Integer> priorityRangeOf(IResource resource, String[] markerTypes, int sizeLimit)
            throws CoreException {

        Set<Integer> priorityLevels = new HashSet<>(sizeLimit);

        for (String markerType : markerTypes) {
            for (IMarker marker : resource.findMarkers(markerType, true, IResource.DEPTH_INFINITE)) {
                priorityLevels.add(rulePriorityFor(marker));
                if (priorityLevels.size() == sizeLimit) {
                    return priorityLevels;
                }
            }
        }

        return priorityLevels;
    }

    public static Set<String> currentRuleNames() {
        gatherRuleNames();
        return rulesByName.keySet();
    }

    private static void gatherRuleNames() {

        rulesByName = new HashMap<>();
        Collection<RuleSet> ruleSets = PMDPlugin.getDefault().getRuleSetManager().getRegisteredRuleSets();
        for (RuleSet rs : ruleSets) {
            for (Rule rule : rs.getRules()) {
                rulesByName.put(rule.getName(), rule);
            }
        }
    }

    private static Rule ruleFrom(IMarker marker) {
        String ruleName = marker.getAttribute(PMDRuntimeConstants.KEY_MARKERATT_RULENAME, "");
        if (StringUtils.isBlank(ruleName)) {
            return null; // printValues(marker);
        }
        return rulesByName.get(ruleName);
    }

    public static Set<IFile> allMarkedFiles(RootRecord root) {

        gatherRuleNames();

        Set<IFile> files = new HashSet<>();

        for (AbstractPMDRecord projectRecord : root.getChildren()) {
            for (AbstractPMDRecord packageRecord : projectRecord.getChildren()) {
                for (AbstractPMDRecord fileRecord : packageRecord.getChildren()) {
                    ((FileRecord) fileRecord).updateChildren();
                    for (AbstractPMDRecord mRecord : fileRecord.getChildren()) {
                        MarkerRecord markerRecord = (MarkerRecord) mRecord;
                        for (IMarker marker : markerRecord.findMarkers()) {
                            Rule rule = ruleFrom(marker);
                            if (rule == null) {
                                continue;
                            }
                            files.add((IFile) fileRecord.getResource());
                        }
                    }
                }
            }
        }

        return files;
    }
}
